﻿using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using Geometry;
using Microsoft.Win32;
using GeoStructor;



namespace D24W3R
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>

    public partial class Window1 : System.Windows.Window
    {
        private GEngine Engine { get; set; }
        
        private Point m_MouseDownPoint;
        private IDraggable m_ItemDragSource;
        private int m_TempZIndex;
        private bool m_ItemDragged;
        private bool m_IsSynchronizingSelection = false;
        private RectangularDragAdorner m_DragSelection;

        public Window1()
        {
            // TODO: Temporary Solution. This prevents the localization of exception messages.
            System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US");
            
            InitializeComponent();
            this.Engine = new GEngine();
            this.DataContext = this.Engine;
        }

        private void Drawings_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_ItemDragSource != null)
            {
                if (m_ItemDragSource.IsDragged) return;
            }

            if (m_IsSynchronizingSelection) return;
            m_IsSynchronizingSelection = true;

            this.xSelectionSettings.SelectedItems.Clear();
            var items = (sender as ListBox).SelectedItems;
            foreach (var item in items)
            {
                this.xSelectionSettings.SelectedItems.Add(item);
            }
            this.Engine.HandleSelection(items);

            m_IsSynchronizingSelection = false;
        }


        private void Settings_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (m_IsSynchronizingSelection) return;
            m_IsSynchronizingSelection = true;

            this.xDrawings.SelectedItems.Clear();
            var items = (sender as ListBox).SelectedItems;
            foreach (var item in items)
            {
                this.xDrawings.SelectedItems.Add(item);
            }
            this.Engine.HandleSelection(items);

            m_IsSynchronizingSelection = false;            
        }

        private void CanExecuteClear(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.xDrawings.SelectedItems.Count > 0;
            e.Handled = true;
        }

        private void ExecutedClear(object sender, ExecutedRoutedEventArgs e)
        {
            this.xDrawings.SelectedItems.Clear();
            e.Handled = true;
        }

        private void CanExecuteDelete(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.xDrawings.SelectedItems.Count > 0;
            e.Handled = true;
        }

        private void ExecutedDelete(object sender, ExecutedRoutedEventArgs e)
        {
            var copy = this.xDrawings.SelectedItems.Cast<IGObject>().ToList();
            this.xDrawings.SelectedItems.Clear();
            if (!this.Engine.DeleteItems(copy))
            {
                copy.ForEach(i => { this.xDrawings.SelectedItems.Add(i); });
                MessageBox.Show(this, 
                    "Some of the selected items are required by some items, outside the selection", 
                    "Deletion failed!", 
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
            }
            e.Handled = true;
        }

        private void ExecutedSave(object sender, ExecutedRoutedEventArgs e)
        {
            var sfd = new SaveFileDialog();
            sfd.Filter = "XGML file| *.XGML";
            if (sfd.ShowDialog() != true) return;
            this.Engine.SaveGraph(sfd.FileName);
            e.Handled = true;
        }

        private void ExecutedLoad(object sender, ExecutedRoutedEventArgs e)
        {
            var ofd = new OpenFileDialog();
            ofd.Filter = "XGML file| *.XGML";
            ofd.CheckFileExists = true;
            if (ofd.ShowDialog() != true) return;
            this.Engine.LoadGraph(ofd.FileName);
            e.Handled = true;
        }

        private void CanExecuteHistory(object sender, CanExecuteRoutedEventArgs e)
        {
            if (e.Parameter is string)
            {
                string param = e.Parameter as string;
                int move = 0;
                if (int.TryParse(param, out move))
                {
                    e.CanExecute = move > 0 ? 
                        this.Engine.Snapshots.Any(gs=>gs.Undone) : 
                        this.Engine.Snapshots.Count(gs=>!gs.Undone) > 1;
                }
            }
            else if (e.Parameter is GraphSnapshot)
            {
                e.CanExecute = this.Engine.Snapshots.Contains(e.Parameter as GraphSnapshot);
            }
            e.Handled = true;
        }


        private void ExecutedHistory(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Parameter is string)
            {
                string param = e.Parameter as string;
                int move = 0;
                if(int.TryParse(param, out move)) this.Engine.MoveInHistory(move);
            }
            else if (e.Parameter is GraphSnapshot)
            {
                this.Engine.MoveToSnapshot(e.Parameter as GraphSnapshot);
            }
            e.Handled = true;
        }

        private void CanExecuteUndo(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.Engine.Snapshots.Count(gs => !gs.Undone) > 1;
            e.Handled = true;
        }


        private void ExecutedUndo(object sender, ExecutedRoutedEventArgs e)
        {
            this.Engine.MoveInHistory(-1);
            e.Handled = true;
        }

        private void CanExecuteRedo(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = this.Engine.Snapshots.Any(gs => gs.Undone);
            e.Handled = true;
        }


        private void ExecutedRedo(object sender, ExecutedRoutedEventArgs e)
        {
            this.Engine.MoveInHistory(+1);
            e.Handled = true;
        }

        private void ExecutedShowDialog(object sender, ExecutedRoutedEventArgs e)
        {
            var type = e.Parameter as Type;
            var dialog = Activator.CreateInstance(type) as Window;
            dialog.Owner = this;
            dialog.ShowDialog();
            e.Handled = true;
        }

        private void Drawings_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            DrawingSettings.PanZoom.TransformX = e.NewSize.Width / 2;
            DrawingSettings.PanZoom.TransformY = e.NewSize.Height / 2;
        }

        private void Drawings_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.MiddleButton == MouseButtonState.Pressed)
            {
                m_MouseDownPoint = e.GetPosition(this.xDrawings);
                this.xDrawings.Cursor = Cursors.ScrollAll;
            }
        }

        private void ExecutedGenerate(object sender, ExecutedRoutedEventArgs e)
        {
            var gen = e.Parameter as GObjectGenerator;
            var newGO = gen.Generate();
            this.Engine.Graph.Add(newGO);
            this.xDrawings.SelectedItem = newGO;
            this.Engine.CreateSnapshot("Added " + newGO.Name);
            e.Handled = true;
        }



        private void ExecutedZoomBy(object sender, ExecutedRoutedEventArgs e)
        {
            ZoomDrawingsBy((double)(e.Parameter));
        }

        private void Drawings_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            ZoomDrawingsBy(e.Delta < 0 ? 1.25 : 0.8);
        }

        private void ZoomDrawingsBy(double factor)
        {
            DrawingSettings.PanZoom.ScaleX *= factor;
            DrawingSettings.PanZoom.ScaleY *= factor;
            DrawingSettings.PanZoom.UserScrollX *= factor;
            DrawingSettings.PanZoom.UserScrollY *= factor;
        }

        private void Drawings_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.MiddleButton != MouseButtonState.Pressed) return;

            Point p = e.GetPosition(this.xDrawings);
            Vector m = p - m_MouseDownPoint;
            DrawingSettings.PanZoom.UserScrollX += m.X;
            DrawingSettings.PanZoom.UserScrollY += m.Y;
            m_MouseDownPoint = p;

            e.Handled = true;
        }

        private void Drawings_MouseUp(object sender, MouseButtonEventArgs e)
        {
            this.xDrawings.Cursor = Cursors.Arrow;
        }

        private void Drawings_PreviewMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton != MouseButtonState.Pressed) return;
            Point pos = e.GetPosition(this.xDrawings);
            Vector move = pos - m_MouseDownPoint;
            if (move.LengthSquared == 0) return;
            if (m_ItemDragSource != null)
            {
                e.Handled = true;
                if (move.LengthSquared == 0) return;
                move.X = move.X / DrawingSettings.PanZoom.ScaleX;
                // mouse coords are upside down
                move.Y = -move.Y / DrawingSettings.PanZoom.ScaleY;
                m_ItemDragSource.DragBy(move);
                m_MouseDownPoint = pos;
                m_ItemDragged = true;
            }
            else
            {
                if (m_DragSelection == null)
                {
                    // selection
                    m_DragSelection = new RectangularDragAdorner(this.xDrawings);
                    m_DragSelection.Start = m_MouseDownPoint;
                    AdornerLayer.GetAdornerLayer(this.xDrawings).Add(m_DragSelection);
                }
                m_DragSelection.Finish = pos;
                var select = new List<IGObject>();
                Rect area = new Rect(m_DragSelection.Start, m_DragSelection.Finish);
                ScaleTransform st = new ScaleTransform(DrawingSettings.PanZoom.ScaleX, DrawingSettings.PanZoom.ScaleY);
                TranslateTransform tt = new TranslateTransform(
                    DrawingSettings.PanZoom.TransformX + DrawingSettings.PanZoom.UserScrollX,
                    DrawingSettings.PanZoom.TransformY + DrawingSettings.PanZoom.UserScrollY);
                MatrixTransform mt = new MatrixTransform(1, 0, 0, -1, 0, 0);
                area.Offset(-2, -2);
                area = tt.Inverse.TransformBounds(area);
                area = mt.TransformBounds(area);
                area = st.Inverse.TransformBounds(area);   
                foreach (var item in this.Engine.Graph)
                {
                    if (item.BoundingBox.HasValue && area.Contains(item.BoundingBox.Value))
                        select.Add(item);
                }
                UpdateSelectionIfNecessary(select);
            }

        }

        private void UpdateSelectionIfNecessary(List<IGObject> select)
        {
            if (this.xDrawings.SelectedItems.Count != select.Count ||
                select.Any(go => !this.xDrawings.SelectedItems.Contains(go)))
            {
                this.xDrawings.SelectedItems.Clear();
                select.ForEach(go => this.xDrawings.SelectedItems.Add(go));
            }
        }

        private void Drawings_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton != MouseButton.Left) return;
            m_MouseDownPoint = e.GetPosition(this.xDrawings);
            VisualTreeHelper.HitTest(this.xDrawings, FilterDraggable, RecordDraggable, new PointHitTestParameters(m_MouseDownPoint));
            if (m_ItemDragSource != null)
            {
                this.xDrawings.Cursor = Cursors.Hand;
                var lbi = this.xDrawings.ItemContainerGenerator.ContainerFromItem(m_ItemDragSource) as ListBoxItem;
                m_TempZIndex = Panel.GetZIndex(lbi);
                Panel.SetZIndex(lbi, int.MaxValue);
                m_ItemDragged = false;
            }
            else
            {
                // wait for move
            }
        }

        private HitTestFilterBehavior FilterDraggable(DependencyObject hit)
        {
            if (hit.GetValue(Draggable.SourceProperty) /*as IDraggable */!= null)
            {
                return HitTestFilterBehavior.Continue;
            }
            else
            {
                return HitTestFilterBehavior.ContinueSkipSelf;
            }
        }

        private HitTestResultBehavior RecordDraggable(HitTestResult htr)
        {
            m_ItemDragSource = htr.VisualHit.GetValue(Draggable.SourceProperty) as IDraggable;
            m_ItemDragSource.BeginDrag();

            return HitTestResultBehavior.Stop; 
        }

        private void Drawings_PreviewMouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton != MouseButton.Left) return;

            if (m_ItemDragSource != null)
            {
                m_ItemDragSource.EndDrag();
                if (m_ItemDragged)
                {
                    if (m_ItemDragSource is IGObject) this.Engine.CreateSnapshot("Item " + (m_ItemDragSource as IGObject).Name + " moved");
                    else this.Engine.CreateSnapshot("Item moved.");
                    this.xSnapshotScroller.ScrollToBottom();
                }
                var lbi = this.xDrawings.ItemContainerGenerator.ContainerFromItem(m_ItemDragSource) as ListBoxItem;
                Panel.SetZIndex(lbi, m_TempZIndex);
                m_ItemDragSource = null;
                this.xDrawings.Cursor = Cursors.Arrow;
            }
            else if (m_DragSelection != null)
            {
                AdornerLayer.GetAdornerLayer(this.xDrawings).Remove(m_DragSelection);
                m_DragSelection.InvalidateVisual();
                m_DragSelection = null;
            }

        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // Workaround for a bug, that puts toolbar items into overflow mode until a resize occurs
            this.xGeneratorToolBar.InvalidateMeasure();
        }


    }

    // Adorners must subclass the abstract base class Adorner.
    public class RectangularDragAdorner : Adorner
    {
        // Be sure to call the base class constructor.
        public RectangularDragAdorner(UIElement adornedElement)
            : base(adornedElement)
        {
            this.IsHitTestVisible = false;
            m_Border = new Pen(SystemColors.HotTrackBrush, 1.0);
        }


        private Point m_Start;
        public Point Start
        {
            get { return m_Start; }
            set
            {
                m_Start = value;
                m_Finish = m_Start;
            }
        }

        private Point m_Finish;
        public Point Finish
        {
            get { return m_Finish; }
            set
            {
                m_Finish = value;
                this.InvalidateVisual();
            }
        }
        public Brush Color
        {
            get { return m_Border.Brush; }
            set
            {
                m_Border.Brush = value;
            }
        }
        public double Thickness
        {
            get { return m_Border.Thickness; }
            set
            {
                m_Border.Thickness = value;
            }
        }
        private Pen m_Border;

        // A common way to implement an adorner's rendering behavior is to override the OnRender
        // method, which is called by the layout system as part of a rendering pass.
        protected override void OnRender(DrawingContext drawingContext)
        {
            // Draw a circle at each corner.
            drawingContext.DrawRectangle(null, m_Border, new Rect(Start, Finish));
        }
    }
}
